﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Ue.Infrastructure.Caching;
using Ue.Infrastructure.Caching.Parallel;

namespace Ue.Tests.Infrastructure.Caching
{
    [TestClass]
    public class ParallelCacheContextTests
    {
        [ThreadStatic]
        private static IAcquireContext acquireContext = null;
        private Mock<ICacheContextAccessor> mockCacheContextAccessor;
        private IParallelCacheContext parallelCacheContex;
        private ICacheManager cacheManager;

        [TestInitialize]
        public void Init()
        {
            this.mockCacheContextAccessor = new Mock<ICacheContextAccessor>();

            this.mockCacheContextAccessor.Setup(item => item.Current)
                .Returns(() => acquireContext);
            this.mockCacheContextAccessor.SetupSet<IAcquireContext>(item => item.Current = It.IsAny<IAcquireContext>())
                .Callback(item => acquireContext = item);

            this.parallelCacheContex = new ParallelCacheContext(this.mockCacheContextAccessor.Object);
            this.cacheManager = new CacheManager(typeof(ParallelCacheContextTests), new CacheHolder(this.mockCacheContextAccessor.Object));
        }

        [TestMethod]
        public void CreateContextAwareTask()
        {
            int conter=0, conter2=0, conter3=0;

            var task = parallelCacheContex.CreateContextAwareTask<object>(() =>
            {
                var obj = this.cacheManager.Get<string, object>("key1", context =>
                {
                    Mock<IVolatileToken> mockToken = new Mock<IVolatileToken>();
                    mockToken.Setup(item => item.IsCurrent)
                        .Returns(() => conter < 1);

                    context.Monitor(mockToken.Object);

                    return new object();
                });

                var obj2 = this.cacheManager.Get<string, object>("key2", context =>
                {
                    Mock<IVolatileToken> mockToken = new Mock<IVolatileToken>();
                    mockToken.Setup(item => item.IsCurrent)
                        .Returns(() => conter2 < 1);

                    context.Monitor(mockToken.Object);

                    var subObj = this.cacheManager.Get<string, object>("sub1", subContext =>
                    {
                        Mock<IVolatileToken> mockSubToken = new Mock<IVolatileToken>();
                        mockSubToken.Setup(item => item.IsCurrent)
                        .Returns(() => conter3 < 1);

                        subContext.Monitor(mockSubToken.Object);

                        return new object();
                    });

                    return this.GetHashCode(subObj, new object());
                });

                return this.GetHashCode(obj, obj2);
            });

            var result = task.Execute();
            Assert.AreEqual(3, task.Tokens.Count());
            Assert.AreEqual(result,task.Execute());
            Assert.AreEqual(6, task.Tokens.Count());

            conter = 1;
            Assert.AreNotEqual(task.Execute(), task.Execute());
            conter = 0;
            Assert.AreEqual(task.Execute(), task.Execute());

            conter2 = 1;
            Assert.AreNotEqual(task.Execute(), task.Execute());
            conter2 = 0;
            Assert.AreEqual(task.Execute(), task.Execute());

            conter3 = 1;
            Assert.AreNotEqual(task.Execute(), task.Execute());
            conter3 = 0;
            Assert.AreEqual(task.Execute(), task.Execute());
        }

        [TestMethod]
        public void RunInParallel()
        {
            int conter = 0, conter2 = 0,conter3 = 0;

            IList<Func<object>> list = new List<Func<object>>()
            {
                () => this.cacheManager.Get("obj1", ctx =>
                {
                    Mock<IVolatileToken> mockToken = new Mock<IVolatileToken>();
                    mockToken.Setup(item => item.IsCurrent).Returns(() => conter < 1);

                    ctx.Monitor(mockToken.Object);

                    return new object();
                }),
                () => this.cacheManager.Get("obj2", ctx =>
                {
                    var subObj= this.cacheManager.Get("obj3", subContext =>
                    {
                        Mock<IVolatileToken> subMockToken = new Mock<IVolatileToken>();
                        subMockToken.Setup(item => item.IsCurrent).Returns(() => conter3 < 1);

                        subContext.Monitor(subMockToken.Object);

                        return new object();
                    });

                    Mock<IVolatileToken> mockToken = new Mock<IVolatileToken>();
                    mockToken.Setup(item => item.IsCurrent).Returns(() => conter2 < 1);

                    ctx.Monitor(mockToken.Object);

                    return this.GetHashCode(subObj, new object());
                })
            };

            Func<object> acquireValue = () => this.cacheManager.Get("key1", ctx =>
            {
                //启用并行计算
                return list.AsParallel()
                    .AsOrdered()
                    .Select(item => item())
                    .ToArray();
            });

            //第一次，创建对象，第二次从缓存中读取所以它们相等
            Assert.AreEqual(acquireValue(),acquireValue());
            //将conter设置为1，那么缓存将一直处于失效状态
            conter = 1;
            //缓存处于失效状态，它们的值应该是不相等的。但是，很明显，这里显示它们的值还是相等的。也就是说缓存token无效。
            Assert.AreEqual(acquireValue(), acquireValue());

            //将缓存设置为有效状态。并全使使用ParallelCacheContex执行并行任务
            conter = 0;
            Func<object> acquireValue2=()=>this.cacheManager.Get("key2",ctx=>{
                return this.parallelCacheContex
                    .RunInParallel(list, item => item())
                    .ToArray();
            });
            Assert.AreEqual(acquireValue2(), acquireValue2());
            conter = 1;
            //token生效了，两个次取得的值不同了。
            Assert.AreNotEqual(acquireValue2(), acquireValue2());

            conter = 0;
            Assert.AreEqual(acquireValue2(), acquireValue2());
            conter3 = 1;
            //多级关联token也生效了，没有问题
            Assert.AreNotEqual(acquireValue2(), acquireValue2());
        }

        private int GetHashCode(object left, object right)
        {
            return ((left.GetHashCode() << 5) + left.GetHashCode()) + right.GetHashCode(); 
        }
    }
}
